#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
#include <signal.h>
#include <limits.h>


#define BUF_SIZE 1024


// Defined an element of the linked list
struct process_linked_element {
  
  int pid;
  time_t s_time;
  struct process * next;
  struct process * prev;

};

//Associate it with a nice typedef
typedef struct process_linked_element process;


// Remove node simply removes the node from a doubly-linked list.
// It does this by making the previous node point to the next node, and vice
// versa, bypassing the current node. It then frees the current node.
int remove_node(process * curr){
  process * p, * n;
  p = (process *) curr->prev;
  n = (process *) curr->next;

  if(p!=NULL){
    p->next = (process *) n;
    }
  
    if(n!=NULL){
    n->prev = (process *) p;
    }
  
  free(curr);

  curr = p;
  return 0;


}


int main(){

  char buf[BUF_SIZE];
  pid_t pid;
  char * tok;
  int t_pid;
  int pids[INT_MAX][3];
  process * curr;
  process * head;
  time_t start = time(NULL);
  

  //Create the head of the linked list (the shell)
  head = (process *) malloc(sizeof(process));
  head->pid = getpid();
  head->prev = NULL;
  head->next = NULL;
  head->s_time = start;
  
  curr = head;

  printf("Welcome to the simple shell!\n");

  while( fgets(buf,  BUF_SIZE, stdin) != NULL){

    //printf("%s%", buf);

    // If the shell receives the command list, then it iterates over the linked
    // list, printing off the PID of each node in the list.
    if(strcmp(buf, "list\n") == 0) {

      curr = head;
 
      printf("Listing the processes...\n");
      while(curr != NULL){

	printf("%d\n", curr->pid);
	curr = (process *) curr->next;


      }      
      
      printf("Done listing\n");
    
    }

    else {

    tok = strtok(buf, " ");


    // If the shell invokes kill, we interpret it as a SIGTERM. It terminates
    // the process, then waits for it to finish as a means of cleaning up
    // the linked list, as well as ensuring there is no zombie process.

    if(strcmp(tok, "kill") == 0){

      t_pid = atoi(strtok(NULL, " "));

      kill(t_pid, SIGTERM);
      printf("Killing PID: %d", t_pid);
  
      }


    // The wait command is used to wait for a specific PID or list of PIDs to
    // finish. After finishing, the shell removes the PID from the linked list
    // that has been created, and terminates the child process.

    else if(strcmp(tok, "wait")== 0 ){
      while(tok = strtok(NULL, " ")){
        t_pid = atoi(tok);
        pid_t result = waitpid(t_pid,0,0);
	
	curr = head;
	int cur_pid =curr->pid;
	while((cur_pid != t_pid)){
	  
	  if(curr->next == NULL){
	    printf("The pid you selected was not created by this shell");
	    break;
	  }

	  else{
	    curr = (process *) curr->next;
	    cur_pid = curr->pid;
	  }
	}

	if(cur_pid == t_pid){

	  remove_node(curr);

	}


        

        } 


      }
    

    // If the shell invokes time, the shell iterates through the linked list,
    // looking for the PID of the process involved. When it finds it, it prints
    // the value of the current time minus the processes start time.

    else if(strcmp(tok, "time") == 0 ){
      t_pid = atoi(strtok(NULL, " "));

      curr = head;
      while((curr!=NULL) && (curr->pid != t_pid)){
	curr = curr->next;
	
      }

      if(curr == NULL)
	printf("PID: %d wasn't created by this shell", t_pid);

      else{
	time_t cur_time = time(NULL);
	time_t start_time = curr->s_time;
	time_t time_diff = cur_time - start_time;


	printf("Current runtime of PID %d: %d", t_pid, time_diff);
      }

    }


    // If the shell invokes sleep, a new process is created, and a node is
    // added into the linked list of processes, recording the pid, start_time,
    // and previous and next nodes.

    else if(strcmp(tok, "sleep") == 0){

      int n = atoi(strtok(NULL, " "));

      int pid = fork();

      // Handle the function if it is a child process (really just sleep)
      
      if(pid==0) {
        sleep(n);
        return 0; 
        }


      // Handle the function if it is a parent process by creating a 
      // process node for the child, appropriately populating it, 
      // then adding it to the linked list.
     
      else {
	
	
	process * cur_next;
	cur_next = (process *) head->next;
        time_t t = time(NULL);

	process * n;
	n= (process *) malloc(sizeof(process));

	n->next = cur_next;
	head->next = n;
	
	n->s_time = t;
	n->pid = pid;
	n->prev = head;


        printf("Start time of PID %d is %d\n", pid, t);

}

     
      }

    }


  }  
  
  return 0;





}
